﻿/*!
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.WebSockets;
using System.Threading.Tasks;
using Pittypat.Configuration;
using System.Web;

namespace Pittypat.Web
{
    /// <summary>
    /// 表示一个请求信息。
    /// </summary>
    sealed class Request
    {
        private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(Request));

        /// <summary>
        /// 用户令牌在表单中的字段名称。这是一个常量字段，值为 "__USER_TOKEN"。
        /// </summary>
        internal const string UserTokenName = "__USER_TOKEN";

        #region 字段

        private string appPath;
        private string abs;
        private Uri uri;
        private RequestForm form;
        private byte[] recvBuffer;
        private WebSocket socket;
        private IAsyncProgress progress;
        private bool isUploadFiles;
        private List<UploadFile> uploadedFiles;
        private FunctionInfo funcInfo;
        private string uploadVirtualPath;
        private long maxUploadSize;
        private string[] allowExtensions;
        private Permission permission;
        private string userToken;
        private HttpRequestMessage requestMessage;

        #endregion

        #region 构造函数

        private Request(HttpRequestMessage msg, RequestForm form, WebSocket socket, bool isUploadFiles, byte[] recvBuffer, IAsyncProgress progress)
        {
            // 获取请求的绝对路径，总是转换为小写形式
            this.abs = msg.RequestUri.AbsolutePath.ToLower().Replace('\\', '/');

            // 获取当前 App 的根路径
            this.appPath = msg.GetConfiguration().VirtualPathRoot.Replace('\\', '/');
            
            if (this.appPath != "/")
            {
                // 去掉 abs 中的 appPath 前缀
                if (this.abs.StartsWith(this.appPath, StringComparison.OrdinalIgnoreCase) &&
                    this.abs[this.appPath.Length] == '/')
                {
                    this.abs = this.abs.Substring(this.appPath.Length);
                }
                else
                {
                    // 路径不是以 appPath 开始的，这是一个无效路径。
                    // 设置为空字符串将导致无法查找到 FunctionInfo，从而发送 NotFound。
                    this.abs = "";
                }
            }

            this.userToken = ParseUserToken(msg, form);
            this.permission = Permission.AnonymousUnauthorized;
            this.uri = msg.RequestUri;
            this.form = form;
            this.socket = socket;
            this.recvBuffer = recvBuffer;
            this.progress = progress;
            this.isUploadFiles = isUploadFiles;
            this.uploadedFiles = null;
            this.funcInfo = null;
            this.uploadVirtualPath = null;
            this.maxUploadSize = 0L;
            this.allowExtensions = null;
            this.requestMessage = msg;
        }

        #endregion

        #region 属性

        internal string AppPath
        {
            get
            {
                return this.appPath;
            }
        }

        internal Uri Uri
        {
            get
            {
                return this.uri;
            }
        }

        internal RequestForm Form
        {
            get
            {
                return this.form;
            }
        }

        internal WebSocket Socket
        {
            get
            {
                return this.socket;
            }
        }

        internal byte[] ReceiveBuffer
        {
            get
            {
                return this.recvBuffer;
            }
        }

        internal IAsyncProgress Progress
        {
            get
            {
                return this.progress;
            }
        }

        internal string LowerAbsPath
        {
            get
            {
                return this.abs;
            }
        }

        internal List<UploadFile> UploadedFiles
        {
            get
            {
                return this.uploadedFiles;
            }
            set
            {
                this.uploadedFiles = value;
            }
        }

        internal string UploadVirtualPath
        {
            get
            {
                return this.uploadVirtualPath;
            }
        }

        internal long MaxUploadSize
        {
            get
            {
                return this.maxUploadSize;
            }
        }

        internal string[] AllowExtensions
        {
            get
            {
                return this.allowExtensions;
            }
        }

        internal string UserToken
        {
            get
            {
                return this.userToken;
            }
        }

        internal IUser User
        {
            get
            {
                return this.permission.User;
            }
        }

        internal FunctionInfo FuncInfo
        {
            get
            {
                return this.funcInfo;
            }
        }

        #endregion

        /// <summary>
        /// 创建目标功能的环境变量。
        /// </summary>
        /// <returns>一个表示目标功能环境变量参数列表的 DynamicModel 对象。</returns>
        DynamicModel CreateFunctionContext()
        {
            @string lang = "";
            @string ip = "";

            // 语言
            var langs = this.requestMessage.Headers.AcceptLanguage;
            if (langs != null && langs.Count > 0)
            {
                StringWithQualityHeaderValue firstLang = null;
                foreach (var item in langs)
                {
                    if (!item.Quality.HasValue || item.Quality >= 1d)
                    {
                        firstLang = item;
                        break;
                    }
                }

                if (firstLang != null)
                {
                    lang = firstLang.Value ?? "";
                }
            }

            // IP 地址
            if (this.requestMessage.Properties.ContainsKey("MS_HttpContext"))
            {
                ip = ((HttpContextWrapper)this.requestMessage.Properties["MS_HttpContext"]).Request.UserHostAddress;
            }
            else if (HttpContext.Current != null)
            {
                ip = HttpContext.Current.Request.UserHostAddress;
            }

            var dm = new DynamicModel();
            dm["lang"] = lang;
            dm["ip"] = ip;
            return dm;
        }

        /// <summary>
        /// 从配置文件中获取对当前请求的功能的配置信息。
        /// </summary>
        void CreateConfigInfo()
        {
            if (this.funcInfo == null)
            {
                return;
            }

            if (this.uploadVirtualPath == null)
            {
                var appConfig = PittypatConfig.Current.Apps[funcInfo.DeveloperCode, funcInfo.AppIdentifier];
                var moduleConfig = appConfig == null ? null : appConfig.Modules[funcInfo.ModuleIdentifier];

                if (moduleConfig == null)
                {
                    this.uploadVirtualPath = "";
                    this.maxUploadSize = 0L;
                    this.allowExtensions = null;
                }
                else
                {
                    var funcConfig = moduleConfig.Functions[funcInfo.Identifier];
                    if (funcConfig == null)
                    {
                        this.uploadVirtualPath = "";
                        this.maxUploadSize = 0L;
                        this.allowExtensions = null;
                    }
                    else
                    {
                        this.uploadVirtualPath = funcConfig.UploadPath ?? "";
                        this.maxUploadSize = Math.Min(WebSocketHelper.MaxUploadSize, Math.Max(funcConfig.MaxUploadSize * 1024L, WebSocketHelper.MinUploadSize));
                        this.allowExtensions = string.IsNullOrEmpty(funcConfig.UploadExtensions) ? null : funcConfig.UploadExtensions.Split(',');
                    }
                }
            }
        }

        // 对用户的权限进行验证
        async Task<string> DemandAsync()
        {
            var authorityService = AuthorityService.Current;

            if (authorityService == null)
            {
                return "没有配置用户权限验证安全服务，请检查配置文件的 authorityService 配置项目。\r\n这是一个配置错误，需要系统管理人员调整配置文件。";
            }

            var permission = this.funcInfo.AllowAnonymous ? Permission.AnonymousAuthorized : Permission.AnonymousUnauthorized;

            var userToken = this.UserToken;

            if (!string.IsNullOrEmpty(userToken))
            {
                // 如果提供了用户令牌，则验证用户的权限，并返回用户信息
                permission = await authorityService.DemandAsync("", userToken, this.funcInfo.Path, this.progress) ?? Permission.AnonymousUnauthorized;
            }

            this.permission = permission;

            // 验证用户是否对目标功能具有权限
            if (!permission.Authorized)
            {
                if (permission.User.Id.HasValue)
                {
                    // 用户对目标功能没有权限
                    return string.Format("当前用户没有权限访问 {0}。\r\n请询问系统管理人员您的权限，必要时可由管理员为您分配该权限。", this.uri.AbsolutePath);
                }
                else
                {
                    // 用户是匿名用户
                    return string.Format("用户未登录或会话已经过期，没有权限访问 {0}。\r\n请重新登录。", this.uri.AbsolutePath);
                }
            }

            // 用户对目标功能拥有权限
            return null;
        }

        // 仅仅在 /notification/ 请求中使用，获取用户信息
        async Task<string> UserInfoAsync()
        {
            var authorityService = AuthorityService.Current;

            if (authorityService == null)
            {
                return "没有配置用户权限验证安全服务，请检查配置文件的 authorityService 配置项目。\r\n这是一个配置错误，需要系统管理人员调整配置文件。";
            }

            var permission = Permission.AnonymousUnauthorized;

            var userToken = this.UserToken;

            if (!string.IsNullOrEmpty(userToken))
            {
                // 如果提供了用户令牌，使用用户令牌获取用户信息
                permission = await authorityService.DemandAsync("", userToken, "", this.progress) ?? Permission.AnonymousUnauthorized;
            }

            this.permission = permission;

            if (!permission.User.Id.HasValue)
            {
                // 用户是匿名用户
                return string.Format("用户未登录或会话已经过期，没有权限访问 {0}。\r\n请重新登录。", this.uri.AbsolutePath);
            }

            // 用户对目标功能拥有权限
            return null;
        }

        /// <summary>
        /// 执行 WebSocket 请求的处理。
        /// </summary>
        /// <returns>异步操作任务。</returns>
        internal async Task ProcessSocketAsync()
        {
            if (this.socket == null)
            {
                throw new InvalidOperationException("应执行 AJAX 处理程序。");
            }

            // 进行移步汇报的进度
            var progress = (WebSocketAsyncProgress)this.progress;

            try
            {
                if (this.isUploadFiles)
                {
                    // 上传文件
                    var info = FunctionInfo.Create(this.abs, false);

                    if (info == null)
                    {
                        // 没有找到目标功能
                        await progress.SendAsync(Message.Create(HttpStatusCode.NotFound));
                    }
                    else
                    {
                        if (info.ModuleInfo.IsMaintenance)
                        {
                            // 目标模块正在维护当中
                            await progress.SendAsync(Message.Create(string.Format("目标功能 {0} 正在维护当中，暂时不可访问：\r\n{1}",
                                this.abs,
                                string.IsNullOrEmpty(info.ModuleInfo.MaintenanceMessage) ? "维护结束后将恢复正常，请稍后再试。" : info.ModuleInfo.MaintenanceMessage)));
                        }
                        else
                        {
                            this.funcInfo = info;

                            // 检查权限
                            var error = await this.DemandAsync();

                            if (string.IsNullOrEmpty(error))
                            {
                                // 权限检查成功，获取上传文件的配置信息
                                this.CreateConfigInfo();
                                // 开始上传文件
                                await UploadSocketHandler.ProcessAsync(this);
                            }
                            else
                            {
                                // 权限检查失败
                                await progress.SendAsync(Message.Create(error));
                            }
                        }
                    }
                }
                else
                {
                    if (this.abs.StartsWith("/user/") || this.abs.StartsWith("/notification/"))
                    {
                        // 不能使用 WebSocket 处理用户和通知
                        await progress.SendAsync(Message.Create(HttpStatusCode.MethodNotAllowed));
                    }
                    else
                    {
                        var info = FunctionInfo.Create(this.abs, true);

                        if (info == null)
                        {
                            // 没有找到目标功能
                            await progress.SendAsync(Message.Create(HttpStatusCode.NotFound));
                        }
                        else
                        {
                            if (info.ModuleInfo.IsMaintenance)
                            {
                                // 目标模块正在维护当中
                                await progress.SendAsync(Message.Create(string.Format("目标功能 {0} 正在维护当中，暂时不可访问：\r\n{1}",
                                    this.abs,
                                    string.IsNullOrEmpty(info.ModuleInfo.MaintenanceMessage) ? "维护结束后将恢复正常，请稍后再试。" : info.ModuleInfo.MaintenanceMessage)));
                            }
                            else
                            {
                                // 创建环境变量
                                ((dynamic)info.FunctionModel).Context = CreateFunctionContext();
                                this.funcInfo = info;

                                // 检查权限
                                var error = await this.DemandAsync();

                                if (string.IsNullOrEmpty(error))
                                {
                                    // 权限检查成功，执行目标功能
                                    await FunctionSocketHandler.ProcessAsync(this);
                                }
                                else
                                {
                                    // 权限检查失败
                                    await progress.SendAsync(Message.Create(error));
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception exc)
            {
                // 处理 WebSocket 请求过程中发生错误
                Log.Fatal(exc.Message, exc);

                // 发送错误信息
                await progress.SendAsync(Message.Create(exc));
            }
        }

        /// <summary>
        /// 执行 AJAX 请求的处理。
        /// </summary>
        /// <returns>结果消息。</returns>
        internal async Task<Message> ProcessAjaxAsync()
        {
            if (this.socket != null)
            {
                throw new InvalidOperationException("应执行 WebSocket 处理程序。");
            }

            try
            {
                if (this.abs.StartsWith("/user/"))
                {
                    // 用户登录、注销、用户信息和通知列表
                    return await UserHttpHandler.ProcessAsync(this);
                }
                else if (this.abs.StartsWith("/notification/"))
                {
                    // 检查用户是否已经登录，如果用户已经登录，则同时获取用户信息
                    var error = await this.UserInfoAsync();

                    if (string.IsNullOrEmpty(error))
                    {
                        // 进入通知处理程序
                        return await NotificationHttpHandler.ProcessAsync(this);
                    }
                    else
                    {
                        // 获取用户信息发生错误
                        return Message.Create(error);
                    }
                }
                else
                {
                    // 普通的功能请求，首先获取目标功能的信息
                    var info = FunctionInfo.Create(this.abs, createModel: true /* 指示为目标功能创建 FunctionModel 对象 */);

                    if (info == null)
                    {
                        // 没有找到目标功能
                        return Message.Create(HttpStatusCode.NotFound);
                    }

                    if (info.ModuleInfo.IsMaintenance)
                    {
                        // 目标模块正在维护当中
                        return Message.Create(string.Format("目标功能 {0} 正在维护当中，暂时不可访问：\r\n{1}",
                            this.abs,
                            string.IsNullOrEmpty(info.ModuleInfo.MaintenanceMessage) ? "维护结束后将恢复正常，请稍后再试。" : info.ModuleInfo.MaintenanceMessage));
                    }

                    // 创建环境变量
                    ((dynamic)info.FunctionModel).Context = CreateFunctionContext();
                    this.funcInfo = info;

                    // 检查用户权限以及加载用户信息
                    var error = await this.DemandAsync();

                    if (string.IsNullOrEmpty(error))
                    {
                        // 进入功能处理程序
                        return await FunctionHttpHandler.ProcessAsync(this);
                    }
                    else
                    {
                        // 检查权限或加载用户信息发生错误
                        return Message.Create(error);
                    }
                }
            }
            catch (Exception exc)
            {
                // 在处理 AJAX 过程中发生错误
                Log.Fatal(exc.Message, exc);

                return Message.Create(exc);
            }
        }

        /// <summary>
        /// 从表单字段和 cookie 中解析用户令牌。
        /// </summary>
        /// <param name="req">目标请求消息。</param>
        /// <param name="form">表单字段列表。</param>
        /// <returns>解析到的用户令牌字符串。</returns>
        /// <remarks>
        /// 如果客户端启用了 COOKIE，则用户令牌将通过表单和 COOKIE 发送到服务器，必须合并这两部分才能得到完整的令牌。
        /// 如果客户端没有启用 COOKIE，则用户令牌将仅仅通过表单发送到服务器。
        /// </remarks>
        static string ParseUserToken(HttpRequestMessage req, RequestForm form)
        {
            // 表单中保存的用户令牌部分
            var formPart = form[UserTokenName];
            if (string.IsNullOrEmpty(formPart))
            {
                return "";
            }

            // COOKIE 列表
            var cookies = req.Headers.GetCookies();
            if (cookies == null)
            {
                return formPart;
            }
            
            // COOKIE 中的用户令牌部分
            var cookiePart = "";
            foreach (var c in cookies)
            {
                if (c.Cookies.Count > 0)
                {
                    foreach (var item in c.Cookies)
                    {
                        if (UserTokenName.Equals(item.Name, StringComparison.InvariantCultureIgnoreCase))
                        {
                            cookiePart = item.Value;
                            break;
                        }
                    }

                    if (!string.IsNullOrEmpty(cookiePart))
                    {
                        break;
                    }
                }
            }

            // 合并两个部分
            return string.IsNullOrEmpty(cookiePart) ? formPart : (formPart + cookiePart);
        }

        /// <summary>
        /// 异步创建一个新的请求对象。
        /// </summary>
        /// <param name="req">原始请求消息。</param>
        /// <param name="progress">异步操作进度。</param>
        /// <param name="socket">如果是 WebSocket 请求，则传递目标 WebSocket。</param>
        /// <param name="subprotocol">WebSocket 的子协议。</param>
        /// <returns>使用 <see cref="Request"/> 封装的请求对象。</returns>
        internal static async Task<Request> CreateAsync(HttpRequestMessage req, IAsyncProgress progress, WebSocket socket = null, string subprotocol = null)
        {
            // 从 WebSocket 中读取数据的缓冲区
            byte[] recvBuffer = null;
            // 是否正在上传文件
            bool isUploadFiles = false;

            if (socket != null)
            {
                // 创建从 socket 中读取数据的缓冲区，缓冲区的尺寸为配置文件中的 socketDataSize
                recvBuffer = new byte[WebSocketHelper.MaxBufferSize];

                // 检查是否正在上传文件
                isUploadFiles = String.Equals(WebSocketHelper.UploadProtocol, subprotocol, StringComparison.OrdinalIgnoreCase);
            }

            // 如果不是上传文件，从请求中读取包含的表单字段
            var fields = await RequestForm.ReadFormAsync(req, isUploadFiles, socket, recvBuffer, progress);

            // 创建新的请求对象
            return new Request(req, fields, socket, isUploadFiles, recvBuffer, progress);
        }
    }
}
